package com.jfirer.validator.descriptor;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.validation.ConstraintValidatorFactory;
import javax.validation.GroupSequence;
import javax.validation.ValidationException;
import javax.validation.metadata.BeanDescriptor;
import javax.validation.metadata.ConstructorDescriptor;
import javax.validation.metadata.MethodDescriptor;
import javax.validation.metadata.MethodType;
import javax.validation.metadata.PropertyDescriptor;
import com.jfirer.validator.ExecutableDescriptorFactory;
import com.jfirer.validator.RedefineDefaultGroupDescriptor;
import com.jfirer.validator.util.Commons;

public class BeanDescriptorImpl extends ElementDescriptorImpl implements BeanDescriptor, RedefineDefaultGroupDescriptor
{
	protected     boolean                                    beanConstrained;
	private       Map<String, PropertyDescriptor>            propertyDescriptors;
	private       Map<Method, MethodDescriptor>              methodDescriptors		= new ConcurrentHashMap<Method, MethodDescriptor>();
	private       Map<Constructor<?>, ConstructorDescriptor> constructorDescriptors	= new ConcurrentHashMap<Constructor<?>, ConstructorDescriptor>();
	private       ExecutableDescriptorFactory                executableDescriptorFactory;
	private final Class<?>                                   rootBeanType;
	private final boolean                                    redefineDefaultGroup;
	private final Set<Class<?>>                              redefineDefaultGroupClasses;
	
	@SuppressWarnings("unchecked")
	public BeanDescriptorImpl(Class<?> beanType, ExecutableDescriptorFactory executableDescriptorFactory, ConstraintValidatorFactory constraintValidatorFactory)
	{
		this.executableDescriptorFactory = executableDescriptorFactory;
		initializeElementDescriptor(beanType, defineConstraintDescriptors(constraintValidatorFactory, beanType.getAnnotations()));
		propertyDescriptors = definePropertyDescriptors(beanType, constraintValidatorFactory);
		methodDescriptors = defineMethodDescriptors(beanType);
		constructorDescriptors = defineConstructorDescriptors(beanType);
		beanConstrained = defineBeanConstrained();
		rootBeanType = beanType;
		redefineDefaultGroup = beanType.isAnnotationPresent(GroupSequence.class) ? true : false;
		if (redefineDefaultGroup)
		{
			redefineDefaultGroupClasses = (Set<Class<?>>) Commons.arrayToSet(beanType.getAnnotation(GroupSequence.class).value());
		}
		else
		{
			redefineDefaultGroupClasses = null;
		}
	}
	
	private boolean defineBeanConstrained()
	{
		if (constraintDescriptors.isEmpty() == false)
		{
			return true;
		}
		for (PropertyDescriptor propertyDescriptor : propertyDescriptors.values())
		{
			if (propertyDescriptor.hasConstraints() || propertyDescriptor.isCascaded())
			{
				return true;
			}
		}
		return false;
	}
	
	private Map<Constructor<?>, ConstructorDescriptor> defineConstructorDescriptors(Class<?> beanType)
	{
		Map<Constructor<?>, ConstructorDescriptor> constructorDescriptors = new ConcurrentHashMap<Constructor<?>, ConstructorDescriptor>();
		for (Constructor<?> constructor : beanType.getConstructors())
		{
			constructorDescriptors.put(constructor, executableDescriptorFactory.getConstructorDescriptor(constructor));
		}
		return constructorDescriptors;
	}
	
	private Map<Method, MethodDescriptor> defineMethodDescriptors(Class<?> beanType)
	{
		Map<Method, MethodDescriptor> methodDescriptors = new ConcurrentHashMap<Method, MethodDescriptor>();
		for (Method method : getAllMehtods(beanType))
		{
			if (Modifier.isPublic(method.getModifiers()) == false || Modifier.isProtected(method.getModifiers()) == false)
			{
				continue;
			}
			methodDescriptors.put(method, executableDescriptorFactory.getMethodDescriptor(method));
		}
		return methodDescriptors;
	}
	
	/**
	 * 获取该类所有方法,包含父类的方法.如果子类重载了父类的方法,则该集合中只有子类的方法
	 * 
	 * @param entityClass
	 * @return
	 */
	private Method[] getAllMehtods(Class<?> entityClass)
	{
		List<Method> methods = new ArrayList<Method>();
		while (entityClass != Object.class && entityClass != null)
		{
			for (Method each : entityClass.getDeclaredMethods())
			{
				boolean match = false;
				for (Method exist : methods)
				{
					if (each.getName().equals(exist.getName()) && each.getParameterTypes().length == exist.getParameterTypes().length)
					{
						Class<?>[] p1 = each.getParameterTypes();
						Class<?>[] p2 = exist.getParameterTypes();
						boolean allHit = true;
						for (int i = 0; i < p1.length; i++)
						{
							if (p1[i] != p2[i])
							{
								allHit = false;
								break;
							}
						}
						match = allHit;
					}
				}
				if (match == false)
				{
					methods.add(each);
				}
			}
			entityClass = entityClass.getSuperclass();
		}
		return methods.toArray(new Method[methods.size()]);
	}
	
	private Map<String, PropertyDescriptor> definePropertyDescriptors(Class<?> beanType, ConstraintValidatorFactory constraintValidatorFactory)
	{
		Map<String, PropertyDescriptor> propertyDescriptors = new HashMap<String, PropertyDescriptor>();
		for (Field field : Commons.getAllFields(beanType))
		{
			propertyDescriptors.put(field.getName(), new PropertyDescriptorImpl(field, constraintValidatorFactory));
		}
		return propertyDescriptors;
	}
	
	@Override
	public boolean isBeanConstrained()
	{
		return beanConstrained;
	}
	
	@Override
	public PropertyDescriptor getConstraintsForProperty(String propertyName)
	{
		return propertyDescriptors.get(propertyName);
	}
	
	@Override
	public Set<PropertyDescriptor> getConstrainedProperties()
	{
		return new HashSet<PropertyDescriptor>(propertyDescriptors.values());
	}
	
	@Override
	public MethodDescriptor getConstraintsForMethod(String methodName, Class<?>... parameterTypes)
	{
		Method method = null;
		Class<?> clazz = rootBeanType;
		while (clazz != rootBeanType)
		{
			try
			{
				method = clazz.getDeclaredMethod(methodName, parameterTypes);
			}
			catch (SecurityException e)
			{
				throw new ValidationException(e);
			}
			catch (NoSuchMethodException e)
			{
				clazz = clazz.getSuperclass();
			}
		}
		if (method == null)
		{
			return null;
		}
		return methodDescriptors.get(method);
	}
	
	@Override
	public Set<MethodDescriptor> getConstrainedMethods(MethodType methodType, MethodType... methodTypes)
	{
		throw new UnsupportedOperationException();
	}
	
	@Override
	public ConstructorDescriptor getConstraintsForConstructor(Class<?>... parameterTypes)
	{
		Constructor<?> constructor = null;
		Class<?> clazz = rootBeanType;
		while (clazz != rootBeanType)
		{
			try
			{
				constructor = clazz.getConstructor(parameterTypes);
			}
			catch (SecurityException e)
			{
				throw new ValidationException(e);
			}
			catch (NoSuchMethodException e)
			{
				clazz = clazz.getSuperclass();
			}
		}
		if (constructor == null)
		{
			return null;
		}
		return constructorDescriptors.get(constructor);
	}
	
	@Override
	public Set<ConstructorDescriptor> getConstrainedConstructors()
	{
		return new HashSet<ConstructorDescriptor>(constructorDescriptors.values());
	}
	
	@Override
	public boolean isReDefineDefaultGroup()
	{
		return redefineDefaultGroup;
	}
	
	@Override
	public Set<Class<?>> getRedefineDefaultGroup()
	{
		return redefineDefaultGroupClasses;
	}
	
}
